package modelo.policiales;

import java.io.File;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import modelo.caracteristicas.Cabello;
import modelo.caracteristicas.Hobby;
import modelo.caracteristicas.Senia;
import modelo.caracteristicas.Sexo;
import modelo.caracteristicas.Vehiculo;
import modelo.delictivas.ITrampa;
import modelo.delictivas.Ladron;
import modelo.delictivas.OrganizacionDelictiva;
import modelo.delictivas.Robo;
import modelo.lugaresYPistas.IRespuesta;

import observadores.Observador;
import observadores.Observable;

import org.w3c.dom.DOMException;
import org.w3c.dom.Document;
import org.w3c.dom.Element;



public class Interpol  extends Observable implements IEmisorOrdenesArresto, Observador{
	
	public static int TIEMPOINVESTIGACION = 154;
	private List<HistorialDePolicia> historialesDePolicias;
	private List<Rango> rangosDisponibles;
	private OrganizacionDelictiva organizacionInvestigada;
	
	
	public Interpol(){
		historialesDePolicias = new ArrayList<HistorialDePolicia>();
		rangosDisponibles = this.generarRangos();
		this.organizacionInvestigada = new OrganizacionDelictiva("Default");
	}
	
	private List<Rango> generarRangos(){
		
		List<Rango> rangos = new ArrayList<Rango>();
		rangos.add(RangoNovato.obtener());
		rangos.add(RangoDetective.obtener());
		rangos.add(RangoInvestigador.obtener());
		rangos.add(RangoSargento.obtener());
		
		return rangos;
	}
	
	public void agregarPolicia(Policia unPolicia){
		
		this.historialesDePolicias.add( new HistorialDePolicia (unPolicia) );	
		unPolicia.setEmisorOrdenArresto(this);
	}
	
	public void registrarNuevoPolicia(String unNombre) throws Exception{
		
		this.agregarPolicia(new Policia (unNombre));
		this.getPolicia(unNombre).agregarObservador(this);
	}
	
	//Utilizado para persistencia.
	private void agregarHistorial(HistorialDePolicia historialNuevo){
		
		this.historialesDePolicias.add(historialNuevo);
		historialNuevo.obtenerPolicia().agregarObservador(this);

	}

	
	public Policia getPolicia(String nombrePolicia) throws Exception{
	
		boolean encontrado = false;
		Policia policia = null;
		HistorialDePolicia historial = null;
		Iterator<HistorialDePolicia> itHistoriales = historialesDePolicias.iterator();
		while( itHistoriales.hasNext() && !encontrado)
		{
			historial = itHistoriales.next();
			if ( historial.obtenerPolicia().getNombre().equals(nombrePolicia) ){
				policia =  historial.obtenerPolicia();
				encontrado = true;
			}
		}
		
		if (!encontrado)
			throw new Exception ("No hay un policia con ese nombre");
		
		return policia;
	}
	
	public void agregarArrestoAlHistorialDe (Policia unPolicia){
		
		HistorialDePolicia historial = this.obtenerHistorial(unPolicia);
		
		historial.agregarArresto();
		
	}
	
	private HistorialDePolicia obtenerHistorial (Policia unPolicia){
		
		boolean encontrado = false;
		HistorialDePolicia historial = null;
		Iterator<HistorialDePolicia> itHistoriales = historialesDePolicias.iterator();
		while( itHistoriales.hasNext() && !encontrado)
		{
			historial = itHistoriales.next();
			if ( historial.obtenerPolicia().getNombre() == unPolicia.getNombre() ){
				encontrado = true;
			}
		}
		if (!encontrado)
			return null;
		
		return historial;	
	}
	
	public int getCantidadDeHistoriales(){
		
		return this.historialesDePolicias.size();
		
	}

	
	public void verificarAscenso(Policia unPolicia) throws Exception{
		
		int arrestos = this.obtenerHistorial(unPolicia).obtenerArrestos();
		
		Rango rango = unPolicia.getRango();
		
		if( rango.validarAscenso(this , arrestos) )
		{
			this.ascenderPolicia(unPolicia);
			unPolicia.notificarAscenso();
		}
		else 
		{
			if(this.obtenerHistorial(unPolicia).obtenerArrestos()<= RangoInvestigador.obtener().getArrestosNecesarios())
				unPolicia.notificarFaltanArrestosParaAscenso(unPolicia.getRango().getArrestosNecesarios() - arrestos);
		}
	}
	
	private void ascenderPolicia(Policia unPolicia) {
		
		int posicionRango = this.rangosDisponibles.indexOf(unPolicia.getRango());
		unPolicia.setRango(this.rangosDisponibles.get( posicionRango +1) );		
	}

	
	
	public boolean correspondeAscenso (RangoNovato rango, int cantidadDeArrestos){
		
		if (cantidadDeArrestos>=rango.getArrestosNecesarios())
			return true;		
		return false;
	}
	
	public boolean correspondeAscenso (RangoDetective rango, int cantidadDeArrestos){
		
		if (cantidadDeArrestos>=rango.getArrestosNecesarios())
			return true;		
		return false;
	}
	
	public boolean correspondeAscenso (RangoInvestigador rango, int cantidadDeArrestos){
		
		if (cantidadDeArrestos>=rango.getArrestosNecesarios())
			return true;		
		return false;
	}
	
	public boolean correspondeAscenso (RangoSargento rango, int cantidadDeArrestos){
		//porque no puede ascender mas de este rango
		return false;
	}

	public void asignarRoboAPolicia(Policia policia, Robo robo, int tiempoDisponible) throws Exception {
		policia.crearInvestigacion(tiempoDisponible, robo);
		//policia.getInvestigacion().agregarObservador(this);
	}

	public boolean existePolicia(String nombrePolicia) {
		
		boolean existe = false;
		HistorialDePolicia historial = null;
		Iterator<HistorialDePolicia> itHistoriales = historialesDePolicias.iterator();
		while( itHistoriales.hasNext() && !existe)
		{
			historial = itHistoriales.next();
			if ( historial.obtenerPolicia().getNombre().equals(nombrePolicia) ){
				existe = true;
			}
		}
		return existe;	
	}
	

	public void setOrganizacionInvestigada(OrganizacionDelictiva organizacionDelictiva) {
		this.organizacionInvestigada = organizacionDelictiva;	
	}

	public OrganizacionDelictiva getOrganizacionInvestigada() {
		return this.organizacionInvestigada;
	}
	
	public OrdenArresto emitirOrdenDeArresto(Sexo sexo, Cabello cabello,
			Vehiculo vehiculo, Senia senia, Hobby hobby) {
		
		Ladron ladron = this.organizacionInvestigada.obtenerLadronConCaracteristicas(sexo, cabello, vehiculo, senia, hobby);
		if(ladron == null)
			return null;
		
		OrdenArresto orden = new OrdenArresto(ladron);
		return orden;
		
	}

	public Element serializar(Document doc) throws DOMException, ParserConfigurationException, TransformerException {
		Element elementoInterpol = doc.createElement("Interpol");
		Element elementoHistoriales = doc.createElement("Historiales");
		elementoInterpol.appendChild(elementoHistoriales);
		for (HistorialDePolicia historiales : this.historialesDePolicias) {
			elementoHistoriales.appendChild(historiales.serializar(doc));
		}
		return elementoInterpol;
	}

	public static Interpol hidratar(Document doc) throws Exception {		
		Interpol nuevaInterpol = new Interpol();
		Element elementoHistoriales = (Element) doc.getElementsByTagName("Historiales").item(0);
		for(int i=0; i< elementoHistoriales.getChildNodes().getLength(); i++) {
			HistorialDePolicia historial = HistorialDePolicia.hidratar((Element) elementoHistoriales.getChildNodes().item(i));
			historial.obtenerPolicia().setEmisorOrdenArresto(nuevaInterpol);
			//Para mantener una unica instancia de OrdenArresto
			if(historial.obtenerPolicia().getInvestigacion() != null)
			{
				if (historial.obtenerPolicia().getInvestigacion().tieneOrdenDeArresto()){
					historial.obtenerPolicia().pedirOrdenArresto();
				}
			}
			nuevaInterpol.agregarHistorial(historial);
		}
		return nuevaInterpol;
	}

	public void guardar() throws ParserConfigurationException, TransformerException {		
		DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();
		DocumentBuilder db = dbf.newDocumentBuilder();
		Document doc = db.newDocument();
		Element interpolSerializada = this.serializar(doc);
		
		doc.appendChild(interpolSerializada);
		TransformerFactory transformerFactory = TransformerFactory.newInstance();
		Transformer transformer = transformerFactory.newTransformer();
		DOMSource source = new DOMSource(doc);
		File archivoDestino = new File("Interpol.xml");
		StreamResult result = new StreamResult(archivoDestino);
		transformer.transform(source, result);
	}
	
	public static Interpol cargar() throws Exception {		
		DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
		DocumentBuilder dBuilder = dbFactory.newDocumentBuilder();
		Document doc = dBuilder.newDocument();
		
		File archivo = new File("Interpol.xml");
		
		if(!archivo.exists())
			new Interpol().guardar();
			
		doc = dBuilder.parse(archivo);
		doc.getDocumentElement().normalize();
		
		return Interpol.hidratar(doc);
		
	}

	@Override
	public void actualizar() {
		// TODO Auto-generated method stub
		
	}

	@Override
	public void ladronEncontrado() {
		this.notificarLadronEncontrado();
	}

	@Override
	public void trampaActivada(ITrampa trampa) {
		this.notificarTrampa(trampa);
	}

	@Override
	public void ladronArrestado() {
		this.notificarLadronArrestado();
	}

	@Override
	public void ladronEscapa() {
		this.notificarLadronEscapa();
	}

	@Override
	public void respuestaRecibida(IRespuesta respuesta) {
		this.notificarRespuestaRecibida(respuesta);
	}

	@Override
	public void ordenArrestoDenegada() {
		this.notificarOrdenDeArrestoDenegada();
	}

	@Override
	public void ordenArrestoObtenida(Ladron ladron) {
		this.notificarOrdenDeArrestoObtenida(ladron);
	}

	@Override
	public void policiaDuerme() {
		this.notificarPoliciaDuerme();
	}

	@Override
	public void jugadorViaja() {
		this.notificarViaje();
	}

	@Override
	public void ascensoDePolicia() {
		this.notificarAscenso();
	}

	@Override
	public void faltanAscensos(int arrestosFaltantes) {
		this.notificarFaltanArrestosParaAscenso(arrestosFaltantes);
	}

	@Override
	public void tiempoTerminado() {
		this.notificarTiempoTerminado();
	}

}
